﻿/// /////////////////////////////////////////////
// 单元名: unitSvrMngProcObj
// 作者: Hawk
// 单元描述
// 日期 : 2023-09-15
/// /////////////////////////////////////////////

{$IFDEF FPC}
{$H+}
{$MODE DELPHI}
{$CODEPAGE UTF8}
{$ENDIF}
unit unitSvrMngProcObj;

interface

uses Windows, unitCommonDataStruct, unitObjBase, unitMsgObj, unitSvrMngCommon, unitAAACommon, unitClientUpdateSvrObj,
  unitClientUpdateObj, unitSvrStaticObj, unitSyncObj, unitDDLinkList, unitAuthRecord, unitMisc, unitIOCPFile,
  unitCommonTypes, unitCommonClasses, unitErrCodeDef, unitNetCommon;

type
  TFILE_LIST = TCommStrArr;

  TREMOTEDESK_CLIENT = record
    ClientID: TID_OBJ;
    RemoteHost: TID_HOST;
  end;

  TSvrMngProcObj = class(TMajorObjBase)
  private
    FCurSyncCntID: TID_OBJ;
    FCurProductID: TID_OBJ;
    FNetQueryObj: TNetQueryObj;
    FSvrStatic: TSvrStaticObj;
    FSyncCenterConfig: TSerialFile;
    FSyncSearchThread: TSearchThread;
    FRegedRemoteDesks: TSerialObj;
    FUpdateableFilePath: commonString;
    FSysTimer, FLastDeskData: longword;
    FRetryVerDownCounter, FRestartDownCounter: byte;
    FIMRoot: boolean;

    function ReplaceFile(UpdatePath: CommonString; UpFileNames, DepFileNames: TFILE_LIST): boolean;
    function GetProcInfo(Obj: TObjBase): TDebugProcObjInfo;
    function GeneralSyncRootPath(sp: TSearchParam): CommonString;
    function GeneralSyncPath(splr, sp: TSerialObj): CommonString;
    function RequestNewPathFile: longword;
    function CanAddHostParam(pnt: TSerialObj; const sid, uid: TID_OBJ; var Obj, EO: TSerialObj): ERRCODE;
    function CanAddUpdateableObj(pnt: TSerialObj; const sid, uid: TID_OBJ; var Obj, EO: TSerialObj): ERRCODE;
    function FetchServerInfo: CommonString;
    function CanAddAuthCode(pnt: TSerialObj; const sid, uid: TID_OBJ; var Obj, eObj: TSerialObj): ERRCODE;
    procedure HostParamUpdated(Sender: TObject; ParObj, Obj: TSerialObj; const sid, uid: TID_OBJ);
    procedure CompireUpdateFile(UpdatePath: CommonString; var UpFileNames, DepFileNames: TFILE_LIST);
    procedure GetTableRecords(rtbl: TStoreList; const ReqSource, ReqUID: TID_OBJ);
    procedure UpdatePrjAdded(Sender: TObject; ParObj, Obj: TSerialObj; const sid, uid: TID_OBJ);
    procedure UpFileDeled(Sender: TObject; Obj: TSerialObj; const sid, uid: TID_OBJ);
    procedure GetLocalFileVerInfo;
    procedure SelfUpdate;
    procedure StartSearchThread;
    procedure StartUpdateableMonitor;
    procedure SyncSearchComplete(Sender: TObject; R: TSyncPath);
    procedure SyncSearchTerminate(Thread: TThread);
    procedure SyncDelPath(sp: TSyncPath);
    procedure SyncFileOpe;
    procedure UpdateableFileChanged(ct: TFileChangeType; oldfilename, newfilename: CommonString);
    procedure AddCurrentPrjs;
    procedure RequestFileSystem(Path: CommonString; const ReqID: TID_OBJ);
    procedure PublishRemoteDesktop(const Remote: TID_HOST; Data: longword = 0;
      Err: ERRCODE = ERRCODE_NOERROR; DeskName: CommonString = '');
    procedure StartSelfUpdate;

    procedure ProcGetProcessInfo(msg: TMsg);
    procedure ProcGetFuncInfo(msg: TMsg);
    procedure ProcGetObjPoolInfo(msg: TMsg);
    procedure ProcRecvNetQuery(msg: TMsg);
    procedure ProcGetProcObj(msg: TMsg);
    procedure ProcSetObjPoolInfo(msg: TMsg);
    procedure ProcUserEvent(msg: TMsg);
    procedure ProcTimer(msg: TMsg);
    procedure ProcFreeObjPool(msg: TMsg);
    procedure ProcGetClientVerInfo(msg: TMsg);
    procedure ProcSendClientFile(msg: TMsg);
    procedure ProcGetSvrStatic(msg: TMsg);
    procedure ProcSvrStaticData(msg: TMsg);
    procedure ProcRootRouted(msg: TMsg);
    procedure ProcGetSyncConfig(msg: TMsg);
    procedure ProcAddSyncConfig(msg: TMsg);
    procedure ProcAddLocalSyncConfig(msg: TMsg);
    procedure ProcRecvSyncConfig(msg: TMsg);
    procedure ProcDelSyncConfig(msg: TMsg);
    procedure ProcSearchThreadTerminate(msg: TMsg);
    procedure ProcAddSyncPathFileEntry(msg: TMsg);
    procedure ProcRecvPathFileEntry(msg: TMsg);
    procedure ProcUpdateSyncFile(msg: TMsg);
    procedure ProcVerifyAuthCode(msg: TMsg);
    procedure ProcLocalDeskStatus(msg: TMsg);
    procedure ProcRemoteDeskStatus(msg: TMsg);
    procedure ProcPathChangeNotify(msg: TMsg);
    procedure ProcSaveConfig(msg: TMsg);
    procedure ProcResFocusEmpty(msg: TMsg);
    procedure ProcRegRemoteDesk(msg: TMsg);
    procedure ProcUnRegRemoteDesk(msg: TMsg);
    procedure ProcRouteFail(msg: TMsg);
    procedure ProcAddRecResp(msg: TMsg);
  protected
    function DoProcessMsg(msg: TMsg): boolean; override;
    procedure DoInit(DrationLastShutdowm: longword); override;
    procedure DoInit2; override;
    procedure DoFinal2; override;
  public
    class function ClassID: longword; override;
    procedure Reset; override;
    procedure Return; override;
    procedure SetProperty(PropertyName, PropertyValue: CommonString); override;

    function IndependenceExecThread: boolean; override;
  end;

implementation

uses SysUtils, {$IFDEF FPC}JwaTlHelp32, JwaPsAPI, JwaWinNT, {$ELSE}tlhelp32, PsApi, {$ENDIF}unitObjPool, unitCommonIntf,
  unitWinAPI, unitClassIDDef, unitCommonMsgProc, unitCompilerCommon, unitLogCommon, unitDevClient,
  unitUniqueDataCommon, unitShareFileObjs;

const
  EXT_OLD = '.old';
  DEFAULT_DATASEQUENCY = 5 * 60; // second
  S_PROPERTY_ROOTPRODUCTID = 'rootproductid';
  S_PROPERTY_PRODUCTID = 'productid';
  S_PROJECTFILES_PATH = 'updateablefilespath';
  S_AUTHLIBFILENAME = 'AuthLib.config';
  S_SYNCFILENAME = 'SyncCenter.config';
  S_DESKTOPFILENAME = 'Desktop.config';
  S_UNKNOWNCOMPANY = '未知公司';
  S_UNKNOWNPROJECT = '未知项目';
  S_LOCALDESKDATA = 'localdeskdata';
  L_DESKDATASIZE = 7 * 1024 * 1024;

  { TSvrMngProcObj }

procedure TSvrMngProcObj.AddCurrentPrjs;
var
  tbl: TStoreList;
  o: TStoreList;
begin
  tbl := ObjTree.FindTable(I_UPDATEABLEPROJECTTABLE);
  if (tbl <> nil) then
  begin
    o := TStoreList(tbl.Item[0]);
    while o <> nil do
    begin
      o.AllowSameName := True;
      o.CanAddRecord := CanAddUpdateableObj;
      o.GetRecords := GetTableRecords;
      InitResource(o.ObjID, MASK_ALL, 0, o.Caption);
      InitPriority(o.ObjID, MASK_ALL, '', MESID_OPERATOR_ACCESS);
      InitPriorityAllOpe(o.ObjID, ID_USERROOT_LOCAL, '');
      o := TStoreList(o.Next);
    end;
  end;
end;

function TSvrMngProcObj.CanAddAuthCode(pnt: TSerialObj; const sid, uid: TID_OBJ; var Obj, eObj: TSerialObj): ERRCODE;
var
  ar: TAuthRecord absolute Obj;
  prj: TUpdateableProject;
begin
  if ar.CompanyName = '' then
    Result := ERRCODE_OBJNOTEXIST
  else
  begin
    prj := TUpdateableProject(ObjTree.FindObj(ar.ProductID, TUpdateableProject.ClassID));
    if prj = nil then
      Result := ERRCODE_PROJECTNOTEXIST
    else
    begin
      ar.CalcAuthCode;
      Result := ERRCODE_NOERROR;
    end;
  end;
end;

function TSvrMngProcObj.CanAddHostParam(pnt: TSerialObj; const sid, uid: TID_OBJ; var Obj, EO: TSerialObj): ERRCODE;
var
  nv: TCommonNameValue absolute Obj;
begin
  Result := ERRCODE_NOERROR;
  if EO = nil then
    nv.Creator := sid;
end;

function TSvrMngProcObj.CanAddUpdateableObj(pnt: TSerialObj; const sid, uid: TID_OBJ;
  var Obj, EO: TSerialObj): ERRCODE;
var
  fs: TStoreList;
  info: TUpdateableFileInfo;
  uo: TUpdateFileObj absolute Obj;
begin
  fs := ObjTree.FindTable(I_UPDATEABLEFILETABLE);
  if fs <> nil then
  begin
    info := TUpdateableFileInfo(ObjTree.FindObj(uo.FileID, TUpdateableFileInfo.ClassID));
    if info <> nil then
      Result := ERRCODE_NOERROR
    else
      Result := ERRCODE_FILENOTEXIST;
  end;
end;

class function TSvrMngProcObj.ClassID: longword;
begin
  Result := CLASSID_SVRMNGPROCOBJ;
end;

procedure TSvrMngProcObj.CompireUpdateFile(UpdatePath: CommonString; var UpFileNames, DepFileNames: TFILE_LIST);
var
  ExPath: CommonString;
  verU, verE: TVER_INFO;
  mtU, mtE: TFileTime;
  i: integer;
  prj: TUpdateableProject;
  ufi: TUpdateableFileInfo;
  fo: TUpdateFileObj;
  su, se: CommonString;

  function DeployFilename: CommonString;
  begin
    if fo.DeployFilename <> '' then
      Result := fo.DeployFilename
    else
      Result := ufi.FileName;
  end;

begin
  SetLength(UpFileNames, 0);
  SetLength(DepFileNames, 0);
  prj := TUpdateableProject(ObjTree.FindObj(FCurProductID, TUpdateableProject.ClassID));
  if prj <> nil then
  begin
    ExPath := ModuleFilePath;
    RegularPath(UpdatePath);
    fo := TUpdateFileObj(prj.Item[0]);
    while fo <> nil do
    begin
      ufi := TUpdateableFileInfo(ObjTree.FindObj(fo.FileID, TUpdateableFileInfo.ClassID));
      if ufi <> nil then
      begin
        su := UpdatePath + ufi.FileName;
        se := ExPath + DeployFilename;
        verU := GetModuleVer(su);
        if verU.Ver = 0 then
        begin
          mtU := GetFileModifyTime(su);
          mtE := GetFileModifyTime(se);
          i := CompareFileTime(@mtU, @mtE);
          if i > 0 then
          begin
            AddStrToArr(UpFileNames, ufi.FileName);
            AddStrToArr(DepFileNames, DeployFilename);
          end;
        end
        else
        begin
          verE := GetModuleVer(se);
          if verU.Ver <> verE.Ver then
          begin
            AddStrToArr(UpFileNames, ufi.FileName);
            AddStrToArr(DepFileNames, DeployFilename);
          end;
        end;
      end;
      fo := TUpdateFileObj(fo.Next);
    end;
  end
  else
    Dbg('Find project %s fail', [FCurProductID.ToString]);
end;

procedure TSvrMngProcObj.SyncDelPath(sp: TSyncPath);
begin
  if sp <> nil then
    Delpath(sp.Str);
end;

procedure TSvrMngProcObj.DoFinal2;
begin
  Dbg('Enter SvrMng Final;', L_DBGLEVEL_OUTPUT);
  ProcSaveConfig(nil);
  if FSyncSearchThread <> nil then
    FSyncSearchThread.Terminate;
  Dbg('Leave SvrMng Final;', L_DBGLEVEL_OUTPUT);
  inherited;
end;

procedure TSvrMngProcObj.DoInit(DrationLastShutdowm: longword);
var
  S: CommonString;

  procedure DelOldFiles;
  var
    sr: TSearchRec;
  begin
    S := ModuleFilePath;
    if FindFirst(S + '*' + EXT_OLD, faAnyFile, sr) = 0 then
    begin
      DeleteFileW(pwidechar(S + sr.Name));
      while FindNext(sr) = 0 do
        DeleteFileW(pwidechar(S + sr.Name));
      FindClose(sr);
    end;
  end;

  procedure ResetSync;
  var
    si: TSyncItem;
    sp: TSearchParam;
    tsp: TSyncPath;
  begin
    Dbg('Enter ResetSync');
    si := TSyncItem(FSyncCenterConfig.Item[0]);
    while si <> nil do
    begin
      Dbg('User %s syncitem count %d', [si.ObjID.ToString, si.Count]);
      sp := TSearchParam(si.Item[0]);
      while sp <> nil do
      begin
        sp.SearchPath := ModuleFilePath + SERVERSTORAGE_ROOT + SYNCCENTER_PATH + sp.ObjID.ToString + PathDelim;
        Dbg('sync path %s', [sp.SearchPath]);
        tsp := TSyncPath(sp.Item[0]);
        if tsp <> nil then
          tsp.Caption := sp.SearchPath;
        sp := TSearchParam(sp.Next);
      end;
      si := TSyncItem(si.Next);
    end;
    Dbg('Exit ResetSync');
  end;

begin
  Dbg('Enter SvrMng Init;', L_DBGLEVEL_OUTPUT);
  FIMRoot := FullObjID.IDDetail.SvrMdl.HostID = HOSTID_ROOT;
  ReqAAAQueryObj;
  LoadLogStrFile(Self, 'svrmng_log.txt');
  DelOldFiles;
  S := GetStorePath(True);
  FSyncCenterConfig.StoreFileName := S + S_SYNCFILENAME;
  FSyncCenterConfig.ReadFromFile;
  RegTableCB(ID_COMMONUSEHOSTTABLE, S_COMMONUSEHOSTTABLE, S + S_DESKTOPFILENAME, TStoreList, nil, nil, nil, nil, nil,
    nil, nil);
  RegTableCB(ID_HOSTPARAMTABLE, S_HOSTPARAMTABLE, S + S_DESKTOPFILENAME, TStoreList, nil, nil, CanAddHostParam, nil,
    nil, HostParamUpdated, nil);
  RegTableCB(ID_REMOTEDESK, S_REMOTEDESK, S + S_DESKTOPFILENAME, TStoreList, nil, nil, nil, nil, nil, nil, nil);
  RegTableCB(ID_INCOMINGFILETABLE, S_INCOMINGFILETABLE, S + S_DESKTOPFILENAME, TStoreList, nil, nil, nil, nil, nil,
    nil, nil);
  SendMsgToObj(OBJID_SVRMODULE, MESID_NET_GETQUERYOBJ);
  if FIMRoot then
  begin
    RegTableCB(ID_AUTHRECORDTABLE, S_AUTHRECORDTABLE, S + S_AUTHLIBFILENAME, TStoreList, nil, nil, CanAddAuthCode, nil,
      nil, nil, nil);
    RegTableCB(I_UPDATEABLEPROJECTTABLE, S_UPDATEABLEPROJECTTABLE, S + S_UPDATEABLEFILENAME, TStoreList, nil, nil, nil,
      nil, nil, UpdatePrjAdded, nil);
    RegTableCB(I_UPDATEABLEFILETABLE, S_UPDATEABLEFILETABLE, S + S_UPDATEABLEFILENAME, TStoreList, nil, nil, nil, nil,
      nil, nil, UpFileDeled);
    AddCurrentPrjs;
    // ResetSync;
    StartUpdateableMonitor;
    ObjPool.SetThreshold(TSyncFile, 50, 500);
  end;
  Dbg('Leave SvrMng Init;', L_DBGLEVEL_OUTPUT);
  inherited;
end;

procedure TSvrMngProcObj.DoInit2;
var
  vs: CommonString;
begin
  InitResource(ID_HOSTPARAMTABLE, MASK_ALL, 0, S_HOSTPARAMTABLE);
  InitPriorityLocalModule(ID_HOSTPARAMTABLE, S_LOCALROOTHOSTPARAMPRIORITY);
  InitPriority(ID_HOSTPARAMTABLE, MASK_ALL, '', MESID_OPERATOR_ACCESS + MESID_OPERATOR_ACCESSSUB);
  InitPriorityAllOpe(ID_HOSTPARAMTABLE, MASK_LOCALALLUID, S_LOCALROOTHOSTPARAMPRIORITY);
  AddHostFunc(ID_SYSDURATION, S_SYSDURATION, '', S_SYSDURATION, vtString);
  InitResource(ID_SYSDURATION, MASK_ALL, 0, S_SYSDURATION);
  InitPriority(ID_SYSDURATION, OBJID_SVRMNGMODULE, '', MESID_OPERATOR_MASK);
  InitPriority(ID_SYSDURATION, MASK_ALL, S_SYSDURATION, MESID_OPERATOR_ACCESS);
  AddHostFunc(ID_DBGOUTPUT, S_DBGOUTPUT, '', S_DBGOUTPUT, vtString);
//  ClearPrioritys(ID_DBGOUTPUT);
  InitPriorityAllOpe(ID_DBGOUTPUT, MASK_LOCALALL, S_DBGOUTPUT);
  vs := VerToStr(GetModuleVer(ExeFileName));
  AddHostFunc(ID_MAINPROCVER, S_MAINPROCVER, vs, S_MAINPROCVER, vtString);
  InitResource(ID_MAINPROCVER, MASK_ALL, 0, S_MAINPROCVER);
  InitPriorityAllOpe(ID_MAINPROCVER, OBJID_SVRMNGMODULE, S_MAINPROCVER);
  InitPriority(ID_MAINPROCVER, MASK_ALL, S_MAINPROCVER, MESID_OPERATOR_ACCESS);
  if FIMRoot then
  begin
    InitResource(I_UPDATEABLEPROJECTTABLE, MASK_ALL, 0, S_UPDATEABLEPROJECTTABLE);
    InitPriority(I_UPDATEABLEPROJECTTABLE, MASK_ALL, '', MESID_OPERATOR_ACCESS);
    InitPriorityAllOpe(I_UPDATEABLEPROJECTTABLE, ID_USERROOT_LOCAL, '');
    InitResource(I_UPDATEABLEFILETABLE, MASK_ALL, 0, S_UPDATEABLEFILETABLE);
    InitPriority(I_UPDATEABLEFILETABLE, MASK_ALL, '', MESID_OPERATOR_ACCESS);
    InitPriorityAllOpe(I_UPDATEABLEFILETABLE, ID_USERROOT_LOCAL, '');
    AddHostFunc(ID_PAUSEUPDATE, S_PAUSEUPDATE, S_SWITCH_OFF, S_PAUSEUPDATE_COMMENT, vtSwitch);
    InitResource(ID_PAUSEUPDATE, MASK_ALLHOSTUID, 0, S_PAUSEUPDATE);
    InitPriorityLocalModule(ID_PAUSEUPDATE, S_PAUSEUPDATE);
    InitPriorityAllOpe(ID_PAUSEUPDATE, ID_USERROOT_LOCAL, S_PAUSEUPDATE_COMMENT);
    InitPriority(ID_SYSDURATION, ID_USERROOT_LOCAL, '', MESID_OPERATOR_MASK);
    InitPriorityAllOpe(ID_MAINPROCVER, ID_USERROOT_ROOTHOST, S_MAINPROCVER);
    GetLocalFileVerInfo;
  end
  else
  begin
    AddHostFunc(ID_LASTUPDATE, S_LASTUPDATE, '', S_LASTUPDATE_COMMENT, vtString);
    AddHostFunc(ID_AUTOUPDATE, S_AUTOUPDATEFLAG, S_SWITCH_ON, S_AUTOUPDATE_COMMENT, vtSwitch);
    InitResource(ID_LASTUPDATE, MASK_ALL, 0, S_LASTUPDATE);
    InitPriorityLocalModule(ID_LASTUPDATE, S_LASTUPDATE);
    InitPriority(ID_LASTUPDATE, MASK_ALL, S_LASTUPDATE_COMMENT, MESID_OPERATOR_ACCESS);
    InitResource(ID_AUTOUPDATE, MASK_ALL, 0, S_AUTOUPDATEFLAG);
    InitPriorityLocalModule(ID_AUTOUPDATE, S_AUTOUPDATEFLAG);
    InitPriority(ID_AUTOUPDATE, MASK_ALLHOSTUID, S_SWITCH_ON, MESID_OPERATOR_ACCESS);
  end;
  InitResource(ID_REMOTEDESK, MASK_ALL, 0, S_REMOTEDESK);
  InitPriorityLocalModule(ID_REMOTEDESK, S_REMOTEDESK);
  InitPriority(ID_REMOTEDESK, MASK_ALLHOSTUID, S_REMOTEDESK, MESID_OPERATOR_ACCESS);
  InitResource(ID_COMMONUSEHOSTTABLE, MASK_ALL, 0, S_COMMONUSEHOSTTABLE);
  InitPriorityLocalModule(ID_COMMONUSEHOSTTABLE, S_COMMONUSEHOSTTABLE);
  AddHostFunc(ID_DESKFLAG, S_DESKFLAG, S_SWITCH_ON, S_DESKFLAG_COMMENT, vtSwitch);
  InitResource(ID_DESKFLAG, MASK_ALL, 0, S_DESKFLAG);
  InitPriorityLocalModule(ID_DESKFLAG, S_DESKFLAG);
  InitPriorityAllOpe(ID_DESKFLAG, ID_USERROOT_LOCAL, S_DESKFLAG_COMMENT);
  AddHostFunc(ID_DESKFUNC, S_DESKFUNC, S_SWITCH_OFF, S_DESKFUNC_COMMENT, vtFunc);
  InitResource(ID_DESKFUNC, MakeObjID(HOSTID_ALLMASK, SVRMNGMODULE_ID), 0, S_DESKFUNC);
  InitPriorityLocalModule(ID_DESKFUNC, S_DESKFUNC);
  InitPriorityAllOpe(ID_DESKFUNC, ID_USERROOT_LOCAL, S_DESKFUNC_COMMENT);
  AddHostFunc(ID_RESTARTSVR, S_RESTARTSVR, S_SWITCH_OFF, S_RESTARTSVR, vtFunc);
  InitResource(ID_RESTARTSVR, MASK_ALL, 0, S_RESTARTSVR);
  InitPriorityLocalModule(ID_RESTARTSVR, S_RESTARTSVR);
  InitPriorityAllOpe(ID_RESTARTSVR, ID_USERROOT_LOCAL, S_RESTARTSVR);
  AddHostFunc(ID_REBOOT, S_REBOOT, S_SWITCH_OFF, S_REBOOT, vtFunc);
  InitResource(ID_REBOOT, MASK_ALL, 0, S_REBOOT);
  InitPriorityLocalModule(ID_REBOOT, S_REBOOT);
  InitPriorityAllOpe(ID_REBOOT, ID_USERROOT_LOCAL, S_REBOOT);
  AddHostFunc(ID_SHUTDOWN, S_SHUTDOWN, S_SWITCH_OFF, S_SHUTDOWN, vtFunc);
  InitResource(ID_SHUTDOWN, MASK_ALL, 0, S_SHUTDOWN);
  InitPriorityLocalModule(ID_SHUTDOWN, S_SHUTDOWN);
  InitPriorityAllOpe(ID_SHUTDOWN, ID_USERROOT_LOCAL, S_SHUTDOWN);
  AddHostFunc(ID_DEVICEINFO, S_DEVICEINFO, FetchServerInfo, S_DEVICEINFO, vtString);
  InitResource(ID_DEVICEINFO, MASK_ALL, 0, S_DEVICEINFO);
  InitPriorityAllOpe(ID_DEVICEINFO, OBJID_SVRMNGMODULE, S_DEVICEINFO);
  InitPriority(ID_DEVICEINFO, MASK_LOCALUI, S_DEVICEINFO, MESID_OPERATOR_ACCESS);
  InitPriority(ID_DEVICEINFO, MASK_ALL, S_DEVICEINFO, MESID_OPERATOR_ACCESS);
  AddHostFunc(ID_HPDEVICENAME, S_HPDEVICENAME, DevName, S_HPDEVICENAME, vtString);
  InitResource(ID_HPDEVICENAME, MASK_ALL, 0, S_HPDEVICENAME);
  InitPriority(ID_HPDEVICENAME, MASK_ALL, S_HPDEVICENAME, MESID_OPERATOR_ACCESS);
  AddHostFunc(ID_HPDEVICETYPE, S_HPDEVICETYPE, '0', S_HPDEVICETYPE, vtString);
  InitResource(ID_HPDEVICETYPE, MASK_ALL, 0, S_HPDEVICETYPE);
  InitPriority(ID_HPDEVICETYPE, MASK_ALL, S_HPDEVICETYPE, MESID_OPERATOR_ACCESS);
  AddHostFunc(ID_HPDEVICEPRDID, S_HPDEVICEPRDID, FCurProductID.ToString, S_HPDEVICEPRDID, vtString);
  InitResource(ID_HPDEVICEPRDID, MASK_ALL, 0, S_HPDEVICEPRDID);
  InitPriority(ID_HPDEVICEPRDID, MASK_ALL, S_HPDEVICEPRDID, MESID_OPERATOR_ACCESS);
  AddHostFunc(ID_HOSTFILESYSTEM, S_HOSTFILESYSTEM, FCurProductID.ToString, S_HOSTFILESYSTEM, vtString);
  InitResource(ID_HOSTFILESYSTEM, MASK_ALL, 0, S_HOSTFILESYSTEM);
  InitPriorityAllOpe(ID_HOSTFILESYSTEM, MASK_LOCALALL, S_HOSTFILESYSTEM);
  SetHostParam(ID_MAINPROCVER, vs);
  AddCurrentPrjs;
  inherited;
end;

function TSvrMngProcObj.DoProcessMsg(msg: TMsg): boolean;
begin
  Result := True;
  case msg.MsgID of
    MESID_ALL_GETFUNCTION_INFO:
      ProcGetFuncInfo(msg);
    MESID_ALL_TIMER:
      ProcTimer(msg);
    MESID_ALL_HOURTIMER:
      ProcSaveConfig(msg);
    MESID_ALL_DESKTOP_STATUS:
      ProcLocalDeskStatus(msg);
    MESID_ALL_ROOTSTATUSCHANGED:
      ProcRootRouted(msg);
    MESID_AAA_USEREVENTNOTIFY:
      ProcUserEvent(msg);
    MESID_NET_SENDQUERYOBJ:
      ProcRecvNetQuery(msg);
    MESID_NET_FILECHANGENOTIFY:
      ProcPathChangeNotify(msg);
    MESID_NET_ROUTEFAILED:
      ProcRouteFail(msg);
    MESID_SVRMNG_GETPROCESSINFO:
      ProcGetProcessInfo(msg);
    MESID_SVRMNG_FREEOBJPOOL:
      ProcFreeObjPool(msg);
    MESID_SVRMNG_GET_OBJPOOLINFO:
      ProcGetObjPoolInfo(msg);
    MESID_SVRMNG_GET_SVRPROCOBJ:
      ProcGetProcObj(msg);
    MESID_SVRMNG_SET_OBJPOOLINFO:
      ProcSetObjPoolInfo(msg);
    MESID_SVRMNG_GETUPDATEVERINFO:
      ProcGetClientVerInfo(msg);
    MESID_SVRMNG_GETUPDATEFILE:
      ProcSendClientFile(msg);
    MESID_SVRMNG_GETSVRSTATIC_INFO:
      ProcGetSvrStatic(msg);
    MESID_SVRMNG_SVRSTATICDATA:
      ProcSvrStaticData(msg);
    MESID_SVRMNG_GET_SYNCCONFIG:
      ProcGetSyncConfig(msg);
    MESID_SVRMNG_ADD_SYNCCONFIG:
      ProcAddSyncConfig(msg);
    MESID_SVRMNG_ADD_LOCALSYNCCONFIG:
      ProcAddLocalSyncConfig(msg);
    MESID_SVRMNG_SENDSYNCFILEINFOS:
      ProcRecvSyncConfig(msg);
    MESID_SVRMNG_DEL_SYNCCONFIG:
      ProcDelSyncConfig(msg);
    MESID_SVRMNG_SEARCH_COMPLATE:
      ProcSearchThreadTerminate(msg);
    MESID_SVRMNG_ADD_SYNCPATHFILEENTRY:
      ProcAddSyncPathFileEntry(msg);
    MESID_SVRMNG_UPDATESYNCPATHFILEENTRY:
      ProcRecvPathFileEntry(msg);
    MESID_SVRMNG_SYNCUPDATEFILE:
      ProcUpdateSyncFile(msg);
    MESID_SVRMNG_VERIFYAUTHCODE:
      ProcVerifyAuthCode(msg);
    MESID_SVRMNG_REMOTEDESKTOPSTATUS:
      ProcRemoteDeskStatus(msg);
    MESID_UNIQUEDATA_RESFOCUSEMPTY:
      ProcResFocusEmpty(msg);
    MESID_UNIQUEDATA_ADDOBJ:
      ProcAddRecResp(msg);
    MESID_SVRMNG_REGREMOTEDESK:
      ProcRegRemoteDesk(msg);
    MESID_SVRMNG_UNREGREMOTEDESK:
      ProcUnRegRemoteDesk(msg);
    else
      Result := inherited;
  end;
end;

function TSvrMngProcObj.FetchServerInfo: CommonString;
const
  S_HOSTNAME: CommonString = '主机名：';
  S_OS: CommonString = '操作系统：';
var
  sio: SYSTEM_INFO;
  ove: unitWinAPI.OSVERSIONINFOEX;
  ms: MEMORYSTATUSEX;
  ProductType: longword;
  nv: TCommonNameValue;
begin
  Result := S_HOSTNAME + DevName + S_CRLF;
  FillChar(ove, SizeOf(ove), 0);
  ove.dwOSVersionInfoSize := SizeOf(ove);
  GetVersionEx(ove);
  if (VER_PLATFORM_WIN32_NT = ove.dwPlatformId) and (ove.dwMajorVersion > 4) then
  begin
    Result := Result + S_OS + 'Windows';
    case ove.dwMajorVersion of
      6, 10, 11:
      begin
        case ove.dwMajorVersion of
          11:
            Result := Result + '11 ';
          10:
            case ove.dwMinorVersion of
              0:
              begin
                if ove.wProductType = VER_NT_WORKSTATION then
                  Result := Result + '10 '
                else
                  Result := Result + 'Server 2019 ';
              end;
            end;
          6:
            case ove.dwMinorVersion of
              0:
              begin
                if ove.wProductType = VER_NT_WORKSTATION then
                  Result := Result + 'Vista '
                else
                  Result := Result + 'Server 2008 ';
              end;
              1:
              begin
                if ove.wProductType = VER_NT_WORKSTATION then
                  Result := Result + '7 '
                else
                  Result := Result + 'Server 2008 R2 ';
              end;
              2:
              begin
                if ove.wProductType = VER_NT_WORKSTATION then
                  Result := Result + '8 '
                else
                  Result := Result + 'Server 2012 ';
              end;
            end;
        end;
        GetProductInfo(ove.dwMajorVersion, ove.dwMinorVersion, 0, 0, ProductType);
        case ProductType of
          PRODUCT_ULTIMATE:
            Result := Result + 'Ultimate Edition';
          PRODUCT_PROFESSIONAL:
            Result := Result + 'Professional';
          PRODUCT_HOME_PREMIUM:
            Result := Result + 'Home Premium Edition';
          PRODUCT_HOME_BASIC:
            Result := Result + 'Home Basic Edition';
          PRODUCT_ENTERPRISE:
            Result := Result + 'Enterprise Edition';
          PRODUCT_BUSINESS:
            Result := Result + 'Business Edition';
          PRODUCT_STARTER:
            Result := Result + 'Starter Edition';
          PRODUCT_CLUSTER_SERVER:
            Result := Result + 'Cluster Server Edition';
          PRODUCT_DATACENTER_SERVER:
            Result := Result + 'Datacenter Edition';
          PRODUCT_DATACENTER_SERVER_CORE:
            Result := Result + 'Datacenter Edition (core installation)';
          PRODUCT_ENTERPRISE_SERVER:
            Result := Result + 'Enterprise Edition';
          PRODUCT_ENTERPRISE_SERVER_CORE:
            Result := Result + 'Enterprise Edition (core installation)';
          PRODUCT_ENTERPRISE_SERVER_IA64:
            Result := Result + 'Enterprise Edition for Itanium-based Systems';
          PRODUCT_SMALLBUSINESS_SERVER:
            Result := Result + 'Small Business Server';
          PRODUCT_SMALLBUSINESS_SERVER_PREMIUM:
            Result := Result + 'Small Business Server Premium Edition';
          PRODUCT_STANDARD_SERVER:
            Result := Result + 'Standard Edition';
          PRODUCT_STANDARD_SERVER_CORE:
            Result := Result + 'Standard Edition (core installation)';
          PRODUCT_WEB_SERVER:
            Result := Result + 'Web Server Edition';
        end;
      end;
      5:
      begin
        case ove.dwMinorVersion of
          0:
          begin
            Result := Result + '2000 ';
            if ove.wProductType = VER_NT_WORKSTATION then
              Result := Result + 'Professional'
            else
            begin
              if ove.wSuiteMask and VER_SUITE_DATACENTER <> 0 then
                Result := Result + 'Datacenter Server'
              else if ove.wSuiteMask and VER_SUITE_ENTERPRISE <> 0 then
                Result := Result + 'Advanced Server'
              else
                Result := Result + 'Server';
            end;
          end;
          1:
          begin
            Result := Result + 'XP ';
            if ove.wSuiteMask and VER_SUITE_PERSONAL <> 0 then
              Result := Result + 'Home Edition'
            else
              Result := Result + 'Professional';
          end;
          2:
          begin
            if GetSystemMetrics(SM_SERVERR2) <> 0 then
              Result := Result + 'Server 2003 R2, '
            else if ove.wSuiteMask and VER_SUITE_STORAGE_SERVER <> 0 then
              Result := Result + 'Storage Server 2003'
            else if ove.wSuiteMask and VER_SUITE_WH_SERVER <> 0 then
              Result := Result + 'Home Server'
            else if (ove.wProductType = VER_NT_WORKSTATION) and (sio.wProcessorArchitecture =
              PROCESSOR_ARCHITECTURE_AMD64) then
              Result := Result + 'XP Professional x64 Edition'
            else
              Result := Result + 'Server 2003, ';
            if ove.wProductType <> VER_NT_WORKSTATION then
            begin
              if sio.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_IA64 then
              begin
                if ove.wSuiteMask and VER_SUITE_DATACENTER <> 0 then
                  Result := Result + 'Datacenter Edition for Itanium-based Systems'
                else if ove.wSuiteMask and VER_SUITE_ENTERPRISE <> 0 then
                  Result := Result + 'Enterprise Edition for Itanium-based Systems';
              end
              else if sio.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64 then
              begin
                if ove.wSuiteMask and VER_SUITE_DATACENTER <> 0 then
                  Result := Result + 'Datacenter x64 Edition'
                else if ove.wSuiteMask and VER_SUITE_ENTERPRISE <> 0 then
                  Result := Result + 'Enterprise x64 Edition'
                else
                  Result := Result + 'Standard x64 Edition';
              end;
            end
            else
            begin
              if ove.wSuiteMask and VER_SUITE_COMPUTE_SERVER <> 0 then
                Result := Result + 'Compute Cluster Edition'
              else if ove.wSuiteMask and VER_SUITE_DATACENTER <> 0 then
                Result := Result + 'Datacenter Edition'
              else if ove.wSuiteMask and VER_SUITE_ENTERPRISE <> 0 then
                Result := Result + 'Enterprise Edition'
              else if ove.wSuiteMask and VER_SUITE_BLADE <> 0 then
                Result := Result + 'Web Edition'
              else
                Result := Result + 'Standard Edition';
            end;
          end;
        end;
      end;
    end;
    if ove.szCSDVersion[0] <> #0 then
      Result := Result + ' ' + ove.szCSDVersion;
    Result := Result + '(build ' + IntToStr(ove.dwBuildNumber) + ')';

    if ove.dwMajorVersion >= 6 then
    begin
      if sio.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_AMD64 then
        Result := Result + ', X64'
      else if sio.wProcessorArchitecture = PROCESSOR_ARCHITECTURE_INTEL then
        Result := Result + ', X86';
    end;
  end;
  ms.dwLength := SizeOf(ms);
  if GlobalMemoryStatusEx(ms) then
    Result := Result + S_CRLF + '已安装内存：' + SizeToStr(ms.ullTotalPhys);
  nv := ObjPool.GetObj(TCommonNameValue, Self);
  nv.ObjID := ID_DEVICEINFO;
  nv.Name := S_DEVICEINFO;
  nv.Value := Result;
  AddRecord(ID_HOSTPARAMTABLE, nv);
end;

procedure TSvrMngProcObj.SyncFileOpe;
var
  si: TSyncItem;
  sp: TSearchParam;
  spr: TSyncPath;
  m: TMsg;
  rta: TRunTimeAccount;

  procedure EnumUpdateFileInPath(tsp: TSyncPath);
  var
    tmp: TSerialObj;
    sf: TSyncFile absolute tmp;
    sp: TSyncPath absolute tmp;
    fc: TFileContent;
    so: TStringObj;
  begin
    tmp := TSerialObj(tsp.Item[0]);
    while tmp <> nil do
    begin
      if tmp.ClassID = TSyncPath.ClassID then
        EnumUpdateFileInPath(sp)
      else
      begin
        if m.Count = 10 then
          Break;
        case sf.SyncOperator of
          soNone:
            ;
          soUpload:
          begin
            fc := ObjPool.GetObj(TFileContent, Self);
            fc.ObjID := tmp.ObjID;
            fc.FileName := GeneralSyncPath(spr, sp);
            m.AddObj(fc);
          end;
          soDownload:
          begin
            so := ObjPool.GetObj(TStringObj, Self);
            so.ObjID := tmp.ObjID;
            m.AddObj(so);
          end;
          soDelete:
            ;
        end;
      end;
      tmp := TSerialObj(tmp.Next);
    end;
  end;

begin
  rta := FNetQueryObj.QueryRuntimeAccount(HOSTID_ROOT);
  if rta <> nil then
  begin
    si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(rta.AccountID));
    if si <> nil then
    begin
      sp := TSearchParam(si.Item[0]);
      while sp <> nil do
      begin
        if sp.CheckFlag(SPFLAG_INUPDATE) then
        begin
          m := GetAMsg;
          m.TargetObjID := ROOTID_SVRMNGMODULE;
          m.MsgID := MESID_SVRMNG_SYNCUPDATEFILE;
          m.ObjID := sp.ObjID;
          spr := sp.Param(TSyncPath);
          if spr <> nil then
            EnumUpdateFileInPath(spr);
          if m.Count > 0 then
            SendMsg(m)
          else
          begin
            sp.ReSetFlag(SPFLAG_INUPDATE);
            ObjPool.ReturnObj(m, Self);
          end;
        end;
        sp := TSearchParam(sp.Next);
      end;
    end;
  end;
end;

function TSvrMngProcObj.GeneralSyncPath(splr, sp: TSerialObj): CommonString;
var
  c: boolean;
begin
  c := TSerialObj(sp).ClassID = TSyncFile.ClassID;
  Result := '';
  while (sp <> nil) and (sp <> splr) do
  begin
    Result := sp.Caption + PathDelim + Result;
    sp := TSyncPath(sp.Parent);
  end;
  Result := splr.Caption + Result;
  if (Result <> '') and c then
    System.Delete(Result, Length(Result), 1);
end;

function TSvrMngProcObj.GeneralSyncRootPath(sp: TSearchParam): CommonString;
begin
  Result := ModuleFilePath + SERVERSTORAGE_ROOT + SYNCCENTER_PATH + sp.ObjID.ToString + PathDelim;
  CreatePath(Result);
end;

procedure TSvrMngProcObj.GetLocalFileVerInfo;
var
  pl: TStoreList;
  cf: TUpdateableFileInfo;
  size: TFILE_SIZE;
  hash: THASH_VAL;
  mt: TFileTime;
  S: CommonString;

  procedure JudgeUpdateMode;
  var
    e: CommonString;
  begin
    e := LowerCase(ExtractFileExt(cf.FileName));
    if (e = '.exe') or (e = '.dll') then
      cf.UpdateMode := umDefault
    else
      cf.UpdateMode := umOnlyNew;
  end;

begin
  Dbg('Updateable project path: %s', [FUpdateableFilePath]);
  pl := ObjTree.FindTable(I_UPDATEABLEFILETABLE);
  if pl <> nil then
  begin
    cf := TUpdateableFileInfo(pl.Item[0]);
    while cf <> nil do
    begin
      S := FUpdateableFilePath + cf.FileName;
      GetFileInfo(S, size, hash, mt);
      cf.FileVer := GetModuleVer(S);
      cf.FileHash := hash;
      cf.ModTime := mt;
      cf.FileSize := size.fp64;
      JudgeUpdateMode;
      cf := TUpdateableFileInfo(cf.Next);
    end;
  end;
end;

function TSvrMngProcObj.GetProcInfo(Obj: TObjBase): TDebugProcObjInfo;
var
  i: integer;
  tmp: TDebugProcObjInfo;
begin
  Result := nil;
  try
    if Obj <> nil then
    begin
      Result := ObjPool.GetObj(TDebugProcObjInfo, Self);
      Result.ObjID := Obj.FullObjID;
      Result.ObjClassName := Obj.ClassName;
      if Obj.Level < 2 then
      begin
        Result.ProcessedMsgCount := TMajorObjBase(Obj).ProcessedMsgCount;
        Result.UnHandledExceptCount := TMajorObjBase(Obj).UnHandledExceptCount;
      end;
      for i := 0 to Obj.Count - 1 do
      begin
        tmp := GetProcInfo(TObjBase(Obj.Item[i]));
        Result.AddObj(tmp);
      end;
    end;
  except

  end;
end;

procedure TSvrMngProcObj.GetTableRecords(rtbl: TStoreList; const ReqSource, ReqUID: TID_OBJ);
var
  tmpp: TUpdateableProject;
  tmpfo: TUpdateFileObj;
  tmpfi: TUpdateableFileInfo;
begin
  tmpp := TUpdateableProject(ObjTree.FindTable(rtbl.ObjID));
  if tmpp <> nil then
  begin
    rtbl.Caption := tmpp.Caption;
    TUpdateableProject(rtbl).DeployPath := tmpp.DeployPath;
    tmpfo := TUpdateFileObj(tmpp.Item[0]);
    while tmpfo <> nil do
    begin
      tmpfi := TUpdateableFileInfo(ObjTree.FindObj(tmpfo.FileID, TUpdateableFileInfo.ClassID));
      if tmpfi <> nil then
      begin
        // tmpfo.FileNameOnSvr := tmpfi.FileName;
        tmpfo.VerOnSvr := tmpfi.FileVer;
        tmpfo.FileHash := tmpfi.FileHash;
        tmpfo.FileSize := tmpfi.FileSize;
        tmpfo.ModTime := tmpfi.ModTime;
        if tmpfo.DeployFilename = '' then
          tmpfo.DeployFilename := tmpfi.FileName;
        tmpfo.UpdateMode := tmpfi.UpdateMode;
        rtbl.AddObj(tmpfo.Clone);
      end;
      tmpfo := TUpdateFileObj(tmpfo.Next);
    end;
  end;
end;

procedure TSvrMngProcObj.HostParamUpdated(Sender: TObject; ParObj, Obj: TSerialObj; const sid, uid: TID_OBJ);
var
  nv: TCommonNameValue absolute Obj;
begin
  if nv.Creator = FullObjID then
  begin
    case Obj.ObjID.IDDetail.SubID.IDBuf[13] of
      $54:
        if nv.Value = S_SWITCH_ON then
          StartSelfUpdate;
      $55:
        if nv.Value <> S_SWITCH_ON then
          SendMsgToObj(MakeObjID(OBJTYPE_ENTITY, 255, HOSTID_EMPTY, EMPTY_ID_MODULE), MESID_ALL_DESKTOP_STOP, []);
      $5C:
        if nv.Value = S_SWITCH_ON then
          SendMsgToObj(MakeObjID(OBJTYPE_ENTITY, 255, HOSTID_EMPTY, EMPTY_ID_MODULE), MESID_ALL_DESKTOP_START, [])
        else
          SendMsgToObj(MakeObjID(OBJTYPE_ENTITY, 255, HOSTID_EMPTY, EMPTY_ID_MODULE), MESID_ALL_DESKTOP_STOP, []);
      $5D:
        if nv.Value = S_SWITCH_ON then
        begin
          nv.Value := S_SWITCH_OFF;
          SendMsgToObj(EMPTY_ID_OBJ, MESID_ALL_TERMINATE);
        end;
      $5E:
        if nv.Value = S_SWITCH_ON then
        begin
          nv.Value := S_SWITCH_OFF;
          ShutDownDevice(False);
        end;
      $5F:
        if nv.Value = S_SWITCH_ON then
        begin
          nv.Value := S_SWITCH_OFF;
          ShutDownDevice(True);
        end;
      $67:
        RequestFileSystem(nv.Value, sid);
    end;
  end
  else
    SendMsgToObj(nv.Creator, MESID_SVRMNG_HOSTPARAMCHANGED, [Obj.Clone]);
end;

// procedure TSvrMngProcObj.ProcAddAuthCode(msg: TMsg);
// var
// ar: TAuthRecord;
// prj: TUpdateableProject;
// begin
// while msg.Count <> 0 do
// begin
// ar := msg.Param(TAuthRecord);
// if ar <> nil then
// begin
// if ar.CompanyName = '' then
// ReturnMsg(msg, MESID_SVRMNG_UPDATEAUTHCODE, ar, 0, ERRCODE_OBJNOTEXIST)
// else
// begin
// prj := FUpdateInfoFile.FindProject(ar.ProductID);
// if prj = nil then
// ReturnMsg(msg, MESID_SVRMNG_UPDATEAUTHCODE, ar, 0, ERRCODE_PROJECTNOTEXIST)
// else
// begin
// ar.CalcAuthCode;
// ObjPool.ReturnObj(FAuthRecordTable.FindItemLowerLevel(ar.ObjID), Self);
// FAuthRecordTable.AddObj(ar);
// ReturnMsg(msg, MESID_SVRMNG_UPDATEAUTHCODE, ar.Clone);
// end;
// end;
// end
// else
// Break;
// end;
// end;

procedure TSvrMngProcObj.ProcAddLocalSyncConfig(msg: TMsg);
var
  si: TSyncItem;
  sp, sp1: TSearchParam;
  m: TMsg;
  rta: TRunTimeAccount;
begin
  rta := FNetQueryObj.QueryRuntimeAccount(HOSTID_ROOT);
  if (rta <> nil) then
  begin
    si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(rta.AccountID));
    if si <> nil then
    begin
      while msg.Count > 0 do
      begin
        sp := msg.Param(TSearchParam);
        if sp <> nil then
        begin
          sp.SearchPath := RegularPath(sp.SearchPath);
          if sp.ObjID.IsEmpty then
            sp.ObjID.GenericID(OBJTYPE_ID, IDSUBTYPE_RECORD);
          sp1 := TSearchParam(si.FindItemLowerLevel(sp.ObjID));
          if sp1 <> nil then
          begin
            sp1.SearchPath := sp.SearchPath;
            sp1.Assign(sp);
            ObjPool.ReturnObj(sp, Self);
            sp := sp1;
          end
          else
            si.AddObj(sp);
          m := GetAMsg;
          m.ObjID := sp.ObjID;
          m.TargetObjID := ROOTID_SVRMNGMODULE;
          m.MsgID := MESID_SVRMNG_GET_SYNCCONFIG;
          m.Data := 1;
          SendMsg(m);
        end
        else
          Break;
      end;
    end;
  end;
end;

procedure TSvrMngProcObj.ProcAddRecResp(msg: TMsg);
var
  so: TStringObj;
  Obj: TSerialObj;
begin
  if msg.Error <> ERRCODE_NOERROR then
    while msg.Count > 0 do
    begin
      so := msg.Param(TStringObj);
      if so <> nil then
      begin
        Obj := so.Obj[0];
        if (Obj <> nil) and (Obj.ObjID = ID_DESKFUNC) then
          PublishRemoteDesktop(msg.SourceObjID.IDDetail.SvrMdl.HostID, I_DESKSTATUS_STARTED, msg.Error);
        ObjPool.ReturnObj(Obj, Self);
        ObjPool.ReturnObj(so, Self);
      end
      else
        Break;
    end;
end;

procedure TSvrMngProcObj.ProcAddSyncConfig(msg: TMsg);
var
  sp, sp1: TSearchParam;
  uid: TID_OBJ;
  si: TSyncItem;
  tsp: TSyncPath;
begin
  uid := UserIDOfSource(msg);
  if (uid.IDDetail.ObjType = OBJTYPE_ID) then
  begin
    Dbg('%s req to add sync, user id: %s', [msg.SourceObjID.IDDetail.SvrMdl.HostID.ToStr, uid.ToString]);
    si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(uid));
    while msg.Count > 0 do
    begin
      sp1 := msg.Param(TSearchParam);
      if sp1 <> nil then
      begin
        ObjPool.ReturnChildObj(sp1, Self);
        sp := TSearchParam(si.FindItemLowerLevel(sp1.ObjID));
        if sp = nil then
        begin
          sp := sp1;
          sp1.ObjID.GenericID(OBJTYPE_ID);
          si.AddObj(sp1);
        end
        else
        begin
          sp.Assign(sp1, True);
          ObjPool.ReturnObj(sp1, Self);
        end;
        ReturnMsg(msg, MESID_SVRMNG_SENDSYNCFILEINFOS, sp.Clone(False));
        sp.SearchPath := GeneralSyncRootPath(sp);
        tsp := sp.Param(TSyncPath);
        if tsp = nil then
        begin
          tsp := ObjPool.GetObj(TSyncPath, Self);
          tsp.ObjID.GenericID(OBJTYPE_ID, IDSUBTYPE_RECORD);
          sp.AddObj(tsp);
        end;
        tsp.Caption := sp.SearchPath;
      end
      else
        Break;
    end;
  end;
end;

procedure TSvrMngProcObj.ProcAddSyncPathFileEntry(msg: TMsg);
var
  oid: TID_OBJ;
  si: TSyncItem;
  sp: TSearchParam;
  spl, splr, spr, tmpr, tmpl, ttmpr: TSerialObj;
  rm: TMsg;
  S: CommonString;
begin
  oid := UserIDOfSource(msg);
  if oid.IDDetail.ObjType = OBJTYPE_ID then
  begin
    si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(oid));
    if si <> nil then
    begin
      sp := TSearchParam(si.FindItemLowerLevel(msg.ObjID));
      if sp <> nil then
      begin
        rm := GetAMsg(msg);
        rm.ObjID := sp.ObjID;
        rm.MsgID := MESID_SVRMNG_UPDATESYNCPATHFILEENTRY;
        splr := TSyncPath(sp.Item[0]);
        if splr = nil then
        begin
          splr := ObjPool.GetObj(TSyncPath, Self);
          splr.Caption := GeneralSyncRootPath(sp);
          sp.AddObj(splr);
        end;
        while msg.Count > 0 do
        begin
          spr := msg.Param(TSyncPath);
          if spr <> nil then
          begin
            if spr.ObjID.IsEmpty then
              spl := splr
            else
              spl := TSyncPath(splr.FindItem(spr.ObjID));
            tmpr := TSyncPath(spr.Item[0]);
            while tmpr <> nil do
            begin
              ttmpr := TSyncPath(tmpr.Next);
              if (tmpr.Caption <> '') then
              begin
                if tmpr.ObjID.IsEmpty then
                begin
                  tmpl := FindEntryInPathByName(TSyncPath(spl), tmpr.Caption, tmpr.ClassID);
                  if tmpl <> nil then
                  begin
                    if tmpl.ObjID.IsEmpty then
                    begin
                      Dbg('Local store OBJID is empty!');
                      tmpl.ObjID.GenericID(OBJTYPE_ID);
                    end;
                    tmpr.ObjID := tmpl.ObjID;
                    tmpr := tmpl;
                  end
                  else
                  begin
                    tmpr.ObjID.GenericID(OBJTYPE_ID);
                    spr.AddObj(tmpr.Clone(False));
                    spl.AddObj(tmpr);
                  end;
                  if tmpr.ClassID = TSyncPath.ClassID then
                  begin
                    S := GeneralSyncPath(splr, tmpr);
                    CreatePath(S);
                  end
                  else if tmpr.ClassID = TSyncFile.ClassID then
                    TSyncFile(Pointer(tmpr)).ResetModFile;
                end
                else
                  ObjPool.ReturnObj(tmpr, Self);
              end
              else
              begin
                Dbg('Found empty path, Parent path = %s', [spr.Caption]);
                ObjPool.ReturnObj(tmpr, Self);
              end;
              tmpr := ttmpr;
            end;
            rm.AddObj(spr);
          end
          else
            Break;
        end;
        if rm.Count > 0 then
          SendMsg(rm)
        else
          ObjPool.ReturnObj(rm, Self);
      end;
    end;
  end;
end;

// procedure TSvrMngProcObj.ProcDelAuthCode(msg: TMsg);
// var
// ar: TAuthRecord;
// rm: TMsg;
// begin
// ar := TAuthRecord(FAuthRecordTable.FindItemLowerLevel(msg.ObjID));
// if ar <> nil then
// begin
// rm := GetAMsg(msg);
// rm.MsgID := MESID_SVRMNG_AUTHCODEDELED;
// rm.ObjID := msg.ObjID;
// ObjPool.ReturnObj(ar, Self);
// SendMsg(rm);
// end
// else
// ReturnMsg(msg, MESID_SVRMNG_AUTHCODEDELED, nil, 0, ERRCODE_OBJNOTEXIST);
// end;

procedure TSvrMngProcObj.ProcDelSyncConfig(msg: TMsg);
var
  so: TStringObj;
  uid: TID_OBJ;
  si: TSyncItem;
  rm: TMsg;
begin
  uid := UserIDOfSource(msg);
  if uid.IDDetail.ObjType = OBJTYPE_ID then
  begin
    si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(uid));
    rm := GetAMsg(msg);
    rm.MsgID := MESID_SVRMNG_SYNCCONFIGDELED;
    if not msg.ObjID.IsEmpty and (si.DelObj(msg.ObjID, msg.SourceObjID, uid) = ERRCODE_NOERROR) then
    begin
      so := ObjPool.GetObj(TStringObj, Self);
      so.ObjID := msg.ObjID;
      rm.AddObj(so);
    end;
    while msg.Count > 0 do
    begin
      so := msg.Param(TStringObj);
      if not so.ObjID.IsEmpty and (si.DelObj(so.ObjID, msg.SourceObjID, uid) = ERRCODE_NOERROR) then
      begin
        rm.AddObj(so);
      end
      else
        ObjPool.ReturnObj(so, Self);
    end;
    SendMsg(rm);
  end;
end;

procedure TSvrMngProcObj.ProcFreeObjPool(msg: TMsg);
begin
  ObjPool.FreeAllObj;
end;

procedure TSvrMngProcObj.ProcGetClientVerInfo(msg: TMsg);
var
  cp: TUpdateableProject;
begin
  Dbg('%s Request Client Ver info', [msg.SourceObjID.ToString, msg.Count]);
  if QueryHostParamValue(ID_PAUSEUPDATE) <> S_SWITCH_ON then
    while msg.Count > 0 do
    begin
      cp := msg.Param(TUpdateableProject);
      if cp <> nil then
      begin
        Dbg('Request %s (%s) Ver info', [cp.Caption, cp.ObjID.ToString]);
        ObjPool.ReturnChildObj(cp, Self);
        GetTableRecords(cp, msg.SourceObjID, msg.SourceObjID);
        ReturnMsg(msg, MESID_SVRMNG_SENDCLIENTVERINFO, cp);
      end
      else
        Break;
    end
  else
    DbgPrint('Pauseclientupdate is True');
end;

procedure TSvrMngProcObj.ProcGetFuncInfo(msg: TMsg);
var
  m: TMsg;
  f: TFuncInfo;
begin
  if AAAQuery.QueryIsInternalUser(msg.SourceObjID) then
  begin
    m := GetAMsg(msg);
    if m <> nil then
    begin
      m.MsgID := MESID_ALL_RETURNFUNC_INFO;
      f := ObjPool.GetObj(TFuncInfo, Self);
      if f <> nil then
      begin
        m.AddObj(f);
        // f.FuncOrOperID := CLASSID_SVRMNGPROCOBJ shl 8;
        f.FuncOrOperDesc := '主机管理模块';

        f := ObjPool.GetObj(TFuncInfo, Self);
        if f <> nil then
        begin
          m.Obj[0].AddObj(f);
          // f.FuncOrOperID := CLASSID_SVRMNGPROCOBJ shl 8 + SVRMNG_MINORFUNC_CLIENTUPDATE;
          f.FuncOrOperDesc := '主机更新模块';
        end;
      end;
      SendMsg(m);
    end;
  end;
end;

procedure TSvrMngProcObj.ProcGetObjPoolInfo(msg: TMsg);
var
  oa: TObjArray;
  tmp: TObjClassInfo;
  opi: TDebugObjPoolInfo;
  droi: TDebugReqObjInfo;
  rr: PREQUEST_OBJ_REC;
  rm: TMsg;
  i: integer;
  j: integer;
  k: integer;
begin
  Dbg('Some %s request ObjPool info', [msg.SourceObjID.ToString]);
  rm := GetAMsg(msg);
  if rm <> nil then
  begin
    rm.MsgID := MESID_SVRMNG_OBJPOOLINFOLIST;
    ObjPool.Lock;
    for i := 0 to ObjPool.Count - 1 do
    begin
      oa := TObjArray(ObjPool.Item[i]);
      for j := 0 to OBJ_ARRAY_CAPACITY - 1 do
      begin
        tmp := oa.GetClsInfo(oa.ClassIDStart + j);
        if tmp <> nil then
        begin
          opi := TDebugObjPoolInfo(ObjPool.GetObj(TDebugObjPoolInfo, Self));
          if opi <> nil then
          begin
            rm.AddObj(opi);
            opi.ClsName := tmp.ClsName;
            opi.ClsID := tmp.ClsID;
            opi.CountInLib := tmp.Count;
            opi.MinThreshold := tmp.MinThreshold;
            opi.MaxThreshold := tmp.MaxThreshold;
            opi.AccessCount := tmp.AccessCount;
            opi.RequestCount := tmp.RequestCount;
            opi.ReturnCount := tmp.ReturnCount;
            opi.CreateCount := tmp.CreateCount;
            opi.FreeCount := tmp.FreeCount;
            opi.ImageFileName := tmp.ImageFileName;

            if msg.Data = 1 then
              for k := 0 to tmp.ReqRecordCount - 1 do
              begin
                droi := TDebugReqObjInfo(ObjPool.GetObj(TDebugReqObjInfo, Self));
                if droi <> nil then
                begin
                  rr := tmp.ReqRecord[k];
                  opi.AddObj(droi);
                  if rr.ReqObj <> nil then
                    droi.ReqClsName := rr.ReqObj.ClassName;
                  droi.RequestCount := rr.ReqCount;
                  droi.ReturnCount := rr.RetCount;
                end;
              end;
          end;
        end;
      end;
    end;
    ObjPool.UnLock;
    SendMsg(rm);
  end;
end;

procedure TSvrMngProcObj.ProcGetProcessInfo(msg: TMsg);
var
  m: TMsg;
  snap: THandle;
  pe32: PROCESSENTRY32W;
  tmp: TSvrProcessInfoObj;
  pm: PROCESS_MEMORY_COUNTERS;
  hp: THandle;
begin
  m := GetAMsg(msg);
  if m <> nil then
  begin
    m.MsgID := MESID_SVRMNG_SENDPROCESSINFO;
    snap := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
    if snap <> INVALID_HANDLE_VALUE then
    begin
      pe32.dwSize := SizeOf(pe32);
      if Process32FirstW(snap, pe32) then
      begin
        repeat
          tmp := TSvrProcessInfoObj(ObjPool.GetObj(TSvrProcessInfoObj, Self));
          if tmp <> nil then
          begin
            tmp.ParentProcessID := pe32.th32ParentProcessID;
            tmp.ThreadCount := pe32.cntThreads;
            tmp.ProcessID := pe32.th32ProcessID;
            tmp.ImagePath := pe32.szExeFile;
            hp := OpenProcess(PROCESS_QUERY_INFORMATION, False, pe32.th32ProcessID);
            if hp <> 0 then
            begin
              if GetProcessMemoryInfo(hp,
                {$IFDEF FPC}
pm
                {$ELSE}
                @pm
                {$ENDIF}
                , SizeOf(pm)) then
                tmp.WorkSetSize := pm.WorkingSetSize;
              CloseHandle(hp);
            end;

            m.AddObj(tmp);
          end;
        until not Process32NextW(snap, pe32);
      end;
      CloseHandle(snap);
    end;
    SendMsg(m);
  end;
end;

procedure TSvrMngProcObj.ProcGetProcObj(msg: TMsg);
var
  tmp: TDebugProcObjInfo;
begin
  tmp := GetProcInfo(TObjBase(Parent));
  ReturnMsg(msg, MESID_SVRMNG_PROCOBJINFO, tmp);
end;

procedure TSvrMngProcObj.ProcGetSvrStatic(msg: TMsg);
begin
  FSvrStatic.StaticTime := Now;
  ReturnMsg(msg, MESID_SVRMNG_SENDSVRSTATICINFO, FSvrStatic.Clone);
end;

procedure TSvrMngProcObj.ProcGetSyncConfig(msg: TMsg);
var
  uid: TID_OBJ;
  si: TSyncItem;
  sp, sp1: TSearchParam;
  rm: TMsg;
  rta: TRunTimeAccount;
begin
  if HostIsMe(msg.SourceObjID.IDDetail.SvrMdl.HostID) then
    FCurSyncCntID := msg.SourceObjID;
  if FIMRoot then
    uid := UserIDOfSource(msg)
  else
  begin
    rta := FNetQueryObj.QueryRuntimeAccount(HOSTID_ROOT);
    if rta <> nil then
      uid := rta.AccountID
    else
      uid.Empty;
  end;
  if uid.IDDetail.ObjType = OBJTYPE_ID then
  begin
    Dbg('%s req Sync config, userID: %S ', [msg.SourceObjID.ToString, uid.ToString]);
    si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(uid));
    rm := GetAMsg(msg);
    rm.MsgID := MESID_SVRMNG_SENDSYNCFILEINFOS;
    sp := TSearchParam(si.Item[0]);
    Dbg('%s req Sync , count %d', [msg.SourceObjID.ToString, si.Count]);
    while sp <> nil do
    begin
      Dbg('%s Sync item %s', [msg.SourceObjID.ToString, sp.ObjID.ToString]);
      if msg.ObjID.IsEmpty or (sp.ObjID = msg.ObjID) then
      begin
        sp1 := TSearchParam(sp.Clone(msg.Data = 1));
        if FIMRoot then
          sp1.SearchPath := '';
        rm.AddObj(sp1);
      end;
      sp := TSearchParam(sp.Next);
    end;
    SendMsg(rm);
  end
  else
    Dbg('%s req Sync but userid not found ', [msg.SourceObjID.ToString]);
end;

procedure TSvrMngProcObj.ProcLocalDeskStatus(msg: TMsg);
var
  m: TMsg;
  so: TStringObj;
  fc: TFileContent;
begin
  case msg.Data of
    I_DESKSTATUS_STARTED:
      SendLog(EMPTY_ID_OBJ, LOGID_SVRMNG_DESKTOPSTATUS_STARTED, LOG_LEVEL_MESSAGE, [IntToHex(msg.Error, 8)]);
    I_DESKSTATUS_STOPED:
      SendLog(EMPTY_ID_OBJ, LOGID_SVRMNG_DESKTOPSTATUS_STOPED, LOG_LEVEL_MESSAGE, [IntToHex(msg.Error, 8)]);
  end;
  m := GetAMsg;
  m.MsgID := MESID_SVRMNG_REMOTEDESKTOPSTATUS;
  m.Data := msg.Data;
  so := msg.Param(TStringObj);
  if so <> nil then
  begin
    FLastDeskData := FSysTimer;
    fc := ObjPool.GetObj(TFileContent, Self);
    fc.ObjID := FullObjID;
    fc.FileName := so.Str;
    m.AddObj(fc);
  end;
  NotifyFocus(ID_DESKFUNC, m);
end;

procedure TSvrMngProcObj.ProcPathChangeNotify(msg: TMsg);
var
  so: TStringObj;
begin
  while msg.Count > 0 do
  begin
    so := msg.Param(TStringObj);
    if so <> nil then
    begin
      UpdateableFileChanged(TFileChangeType(so.Flag), so.Caption, so.Str);
      ObjPool.ReturnObj(so, Self);
    end
    else
      Break;
  end;
end;

procedure TSvrMngProcObj.ProcRecvNetQuery(msg: TMsg);
begin
  ObjPool.ReturnObj(FNetQueryObj, Self);
  FNetQueryObj := msg.Param(TNetQueryObj);
  msg.DelObj(FNetQueryObj);
end;

procedure TSvrMngProcObj.ProcRecvPathFileEntry(msg: TMsg);
var
  si: TSyncItem;
  sp: TSearchParam;
  spr, spe, splr, sple, tmpr: TSerialObj;
  rta: TRunTimeAccount;
begin
  rta := FNetQueryObj.QueryRuntimeAccount(HOSTID_ROOT);
  if rta <> nil then
  begin
    si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(rta.AccountID));
    if si <> nil then
    begin
      sp := TSearchParam(si.FindItemLowerLevel(msg.ObjID));
      if sp <> nil then
      begin
        splr := TSerialObj(sp.Item[0]);
        while msg.Count > 0 do
        begin
          spr := msg.Param(TSyncPath);
          if spr <> nil then
          begin
            if spr.ObjID.IsEmpty then
              tmpr := splr
            else
              tmpr := TSerialObj(splr.FindItem(spr.ObjID));
            while spr.Count > 0 do
            begin
              spe := TSerialObj(spr.Item[0]);
              if spe <> nil then
              begin
                sple := FindEntryInPathByName(tmpr, spe.Caption, spe.ClassID);
                if sple <> nil then
                  sple.ObjID := spe.ObjID;
                ObjPool.ReturnObj(spe, Self);
              end;
            end;
            ObjPool.ReturnObj(spr, Self);
          end
          else
            Break;
        end;
        RequestNewPathFile;
      end;
    end;
  end;
end;

procedure TSvrMngProcObj.ProcRemoteDeskStatus(msg: TMsg);
var
  fc: TFileContent;
begin
  fc := msg.Param(TFileContent);
  if fc <> nil then
    PublishRemoteDesktop(msg.SourceObjID.IDDetail.SvrMdl.HostID, msg.Data, msg.Error, fc.FileName)
  else
    PublishRemoteDesktop(msg.SourceObjID.IDDetail.SvrMdl.HostID, msg.Data, msg.Error);
end;

procedure TSvrMngProcObj.ProcRecvSyncConfig(msg: TMsg);
var
  si: TSyncItem;
  sp, spl: TSearchParam;
  rta: TRunTimeAccount;

  procedure ResetAllFileOperator(Path: TSerialObj);
  var
    sf: TSerialObj;
  begin
    if Path <> nil then
    begin
      sf := TSerialObj(Path.Item[0]);
      while sf <> nil do
      begin
        if sf.ClassID = CLASSID_SYNC_FILE then
          TSyncFile(sf).SyncOperator := soDownload
        else
          ResetAllFileOperator(TSyncPath(Pointer(sf)));
        sf := TSerialObj(sf.Next);
      end;
    end;
  end;

begin
  rta := FNetQueryObj.QueryRuntimeAccount(HOSTID_ROOT);
  if (FSyncSearchThread = nil) and (rta <> nil) then
  begin
    si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(rta.AccountID));
    if si <> nil then
      while msg.Count > 0 do
      begin
        sp := msg.Param(TSearchParam);
        if sp <> nil then
        begin
          spl := TSearchParam(si.FindItemLowerLevel(sp.ObjID));
          si.AddObj(sp);
          if spl <> nil then
          begin
            sp.SearchPath := spl.SearchPath;
            ObjPool.ReturnObj(spl, Self);
          end;
          if sp.SearchPath <> '' then
          begin
            sp.SetFlag(SPFLAG_WAITENUM);
            ResetAllFileOperator(sp.Param(TSyncPath));
          end;
        end
        else
          Break;
      end;
    StartSearchThread;
  end;
end;

procedure TSvrMngProcObj.ProcRegRemoteDesk(msg: TMsg);
var
  oid: TID_OBJ;
  so, so1: TStringObj;
  objs: TObjIDArr;
begin
  while msg.Count > 0 do
  begin
    so := msg.Param(TStringObj);
    if so <> nil then
    begin
      ObjPool.TempPath := so.Str;
      oid := MakeObjID(so.ObjID.IDDetail.SvrMdl.HostID, UNIQUEDATAMODULE_ID);
      so1 := TStringObj(FRegedRemoteDesks.FindItemLowerLevel(oid));
      if so1 = nil then
      begin
        so.ObjID := oid;
        FRegedRemoteDesks.AddObj(so);
        SetLength(objs, 1);
        objs[0] := ID_DESKFUNC;
        FocusResource(so.ObjID.IDDetail.SvrMdl.HostID, objs);
        so1 := ObjPool.GetObj(TStringObj, Self);
        so1.ObjID := msg.SourceObjID;
        so.AddObj(so1);
      end
      else
      begin
        so1.Str := so.Str;
        ObjPool.ReturnObj(so, Self);
      end;
      StartOrStopHostFunc(so.ObjID.IDDetail.SvrMdl.HostID, ID_DESKFUNC, True);
    end
    else
      Break;
  end;
end;

procedure TSvrMngProcObj.ProcResFocusEmpty(msg: TMsg);
begin
  if msg.ObjID = ID_DESKFUNC then
  begin
    Dbg('Because nobody focus on Desktop, so close');
    SendMsgToObj(MakeObjID(OBJTYPE_ENTITY, 255, HOSTID_EMPTY, EMPTY_ID_MODULE), MESID_ALL_DESKTOP_STOP, []);
  end;
end;

procedure TSvrMngProcObj.ProcSaveConfig(msg: TMsg);
begin
  FSyncCenterConfig.WriteToFile;
end;

procedure TSvrMngProcObj.ProcSearchThreadTerminate(msg: TMsg);
begin
  RequestNewPathFile;
end;

procedure TSvrMngProcObj.ProcSendClientFile(msg: TMsg);
var
  S: TStringObj;
  cf: TUpdateableFileInfo;
  fc: TFileContent;
  fn: CommonString;
  m: TMsg;
begin
  m := GetAMsg(msg);
  m.MsgID := MESID_SVRMNG_SENDFILECONTENT;
  while msg.Count > 0 do
  begin
    S := msg.Param(TStringObj);
    if S <> nil then
    begin
      cf := TUpdateableFileInfo(ObjTree.FindObj(S.ObjID, TUpdateableFileInfo.ClassID));
      if cf <> nil then
      begin
        Dbg('%s Request file %s(%s)', [msg.SourceObjID.ToString, cf.FileName, cf.ObjID.ToString]);
        fn := FUpdateableFilePath + cf.FileName;
        fc := TFileContent(ObjPool.GetObj(TFileContent, Self));
        if fc <> nil then
        begin
          fc.ObjID := S.ObjID;
          fc.FileName := fn;
          m.AddObj(fc);
        end;
      end;
      ObjPool.ReturnObj(S, Self);
    end
    else
      Break;
  end;
  SendMsg(m);
end;

procedure TSvrMngProcObj.ProcSetObjPoolInfo(msg: TMsg);
var
  tmp: TDebugObjPoolInfo;
begin
  while msg.Count > 0 do
  begin
    tmp := msg.Param(TDebugObjPoolInfo);
    if tmp <> nil then
    begin
      ObjPool.SetThreshold(tmp.ClsID, tmp.MinThreshold, tmp.MaxThreshold);
      ObjPool.ReturnObj(tmp, Self);
    end
    else
      Break;
  end;
end;

procedure TSvrMngProcObj.ProcSvrStaticData(msg: TMsg);
var
  ssd: TSVR_STATIC_DATA;
begin
  ssd.RollValue := msg.Data;
  case ssd.StaticID of
    SVRSTATICDATAID_INPUTCONN:
      FSvrStatic.InputConnCount := FSvrStatic.InputConnCount + ssd.Value;
    SVRSTATICDATAID_OUTPUTCONN:
      FSvrStatic.OutputConnCount := FSvrStatic.OutputConnCount + ssd.Value;
    SVRSTATICDATAID_INPUTDATA:
      FSvrStatic.InputDataCount := FSvrStatic.InputDataCount + ssd.Value;
    SVRSTATICDATAID_OUTPUTDATA:
      FSvrStatic.OutputDataCount := FSvrStatic.OutputDataCount + ssd.Value;
  end;
end;

procedure TSvrMngProcObj.ProcTimer(msg: TMsg);
var
  S: CommonString;
  m: TMsg;
  nv: TCommonNameValue;
begin
  FSysTimer := msg.Data;
  nv := ObjPool.GetObj(TCommonNameValue, Self);
  nv.ObjID := ID_SYSDURATION;
  nv.Value := IntToStr(FSysTimer);
  AddRecord(ID_HOSTPARAMTABLE, nv);
  // FServerInfoObj.SvrTimer := FServerInfoObj.SvrTimer + 1;
  FSvrStatic.ContinueTime := FSvrStatic.ContinueTime + 1;
  if FRetryVerDownCounter > 0 then
  begin
    if FRestartDownCounter <> 0 then
      FRetryVerDownCounter := 0
    else
    begin
      Dec(FRetryVerDownCounter);
      Dbg('Retreate version left %d', [FRetryVerDownCounter]);
      if FRetryVerDownCounter = 0 then
        GetLocalFileVerInfo;
    end;
  end;
  if FRestartDownCounter > 0 then
  begin
    Dec(FRestartDownCounter);
    S := Format('主机将在 %d 秒后关闭', [FRestartDownCounter]);
    Dbg('Restart program left %d', [FRestartDownCounter]);
    m := GetAMsg;
    m.MsgID := MESID_ALL_SHOWTOPMESSAGE;
    m.Caption := S;
    SendMsg(m);
    if FRestartDownCounter = 0 then
    begin
      SelfUpdate;
      SendMsgToObj(EMPTY_ID_OBJ, MESID_ALL_TERMINATE);
    end;
  end;
end;

procedure TSvrMngProcObj.ProcUnRegRemoteDesk(msg: TMsg);
var
  oid: TID_OBJ;
  so1: TStringObj;
begin
  oid.Empty;
  oid.IDDetail.SvrMdl.HostID := msg.ObjID.IDDetail.SvrMdl.HostID;
  oid.IDDetail.SvrMdl.ModuleID := UNIQUEDATAMODULE_ID;
  so1 := TStringObj(FRegedRemoteDesks.FindItemLowerLevel(oid));
  if so1 <> nil then
  begin
    ObjPool.ReturnObj(so1.FindItemLowerLevel(msg.SourceObjID), Self);
    if so1.Count = 0 then
    begin
      StartOrStopHostFunc(msg.ObjID.IDDetail.SvrMdl.HostID, ID_DESKFUNC, False);
      ObjPool.ReturnObj(so1, Self);
    end;
  end;
end;

procedure TSvrMngProcObj.ProcUpdateSyncFile(msg: TMsg);
var
  si: TSyncItem;
  sp: TSearchParam;
  spr: TSyncPath;
  spe: TSyncFile;
  lv: TSerialObj;
  so: TStringObj absolute lv;
  tso, sso: TStringObj;
  rm: TMsg;
  fc: TFileContent;
  S: CommonString;
  e: longword;
  rta: TRunTimeAccount;
begin
  si := nil;
  if FullObjID.IDDetail.SvrMdl.HostID = HOSTID_ROOT then
    si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(UserIDOfSource(msg)))
  else
  begin
    rta := FNetQueryObj.QueryRuntimeAccount(HOSTID_ROOT);
    if rta <> nil then
      si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(rta.AccountID));
  end;
  if si <> nil then
  begin
    sp := TSearchParam(si.FindItemLowerLevel(msg.ObjID));
    if sp <> nil then
    begin
      spr := sp.Param(TSyncPath);
      if spr <> nil then
      begin
        rm := GetAMsg(msg);
        rm.MsgID := MESID_SVRMNG_SYNCUPDATEFILE;
        rm.ObjID := sp.ObjID;
        while msg.Count > 0 do
        begin
          lv := TSerialObj(msg.Item[0]);
          spe := TSyncFile(spr.FindItem(so.ObjID));
          if spe <> nil then
          begin
            if lv.ClassID = TStringObj.ClassID then
            begin
              if so.Str <> '' then
                spe.SyncOperator := soNone
              else
              begin
                S := GeneralSyncPath(spr, TSyncPath(Pointer(spe)));
                if FileExists(S) then
                begin
                  fc := ObjPool.GetObj(TFileContent, Self);
                  fc.ObjID := spe.ObjID;
                  fc.FileName := S;
                  rm.AddObj(fc);
                end
                else
                  ObjPool.ReturnObj(spe, Self);
              end;
            end
            else if lv.ClassID = TFileContent.ClassID then
            begin
              fc := TFileContent(Pointer(so));
              S := GeneralSyncPath(spr, TSyncPath(Pointer(spe)));
              if not DeleteFileW(pwidechar(S)) then
                CreatePath(S);
              if MoveFile(fc.FileName, S) = ERRCODE_NOERROR then
              begin
                Dbg('Recv file: %s from %s', [S, msg.SourceObjID.ToString]);
                if not FCurSyncCntID.IsEmpty then
                begin
                  sso := ObjPool.GetObj(TStringObj, Self);
                  sso.Str := Format('收到文件 %s', [S]);
                  SendMsgToObj(FCurSyncCntID, MESID_SVRMNG_SYNCPROGRESS, [sso]);
                end;
                spe.ModTime := fc.FileModTime;
                spe.SyncOperator := soNone;
                tso := ObjPool.GetObj(TStringObj, Self);
                tso.ObjID := fc.ObjID;
                tso.Str := '1';
                rm.AddObj(tso);
              end
              else
              begin
                e := GetLastError;
                Dbg('Recv file faile : %s (%d) from %s', [S, e, msg.SourceObjID.ToString]);
                if not FCurSyncCntID.IsEmpty then
                begin
                  sso := ObjPool.GetObj(TStringObj, Self);
                  sso.Str := Format('接收文件错误：%s %d', [S, e]);
                  SendMsgToObj(FCurSyncCntID, MESID_SVRMNG_SYNCPROGRESS, [sso]);
                end;
              end;
            end;
          end;
          ObjPool.ReturnObj(lv, Self);
        end;
        if FullObjID.IDDetail.SvrMdl.HostID = HOSTID_ROOT then
          SendMsg(rm)
        else
        begin
          ObjPool.ReturnObj(rm, Self);
          SyncFileOpe;
        end;
      end;
    end;
  end;
end;

procedure TSvrMngProcObj.ProcUserEvent(msg: TMsg);
var
  uen: TUserMngEventNotify;
begin
  uen := msg.Param(TUserMngEventNotify);
  case uen.Event of
    ueCreated:
      FSyncCenterConfig.InitTable(GenSyncTableIDByUID(uen.ObjID), S_CURUSERSYNCTABLE, TStoreList);
    ueModifyed:
      ;
    ueDeleted:
      ;
    ueOnLine:
      ;
    ueOffLine:
      ;
  end;
end;

procedure TSvrMngProcObj.ProcVerifyAuthCode(msg: TMsg);
var
  ar, ar1: TAuthRecord;
  prj: TUpdateableProject;
  cn, pn: CommonString;
  uid: TID_OBJ;
begin
  while msg.Count > 0 do
  begin
    ar := msg.Param(TAuthRecord);
    if ar <> nil then
    begin
      ar.VerifyResult := ERRCODE_AUTHCODEERR;
      uid.FromStr(ar.SerialNo);
      ar1 := TAuthRecord(ObjTree.FindObj(uid, TAuthRecord.ClassID));
      if (ar1 <> nil) and (ar.AuthCode = ar1.AuthCode) then
        ar.VerifyResult := ERRCODE_NOERROR;
      if ar1 <> nil then
        cn := ar1.CompanyName
      else
        cn := S_UNKNOWNCOMPANY;
      prj := TUpdateableProject(ObjTree.FindObj(ar.ProductID, TUpdateableProject.ClassID));
      if prj <> nil then
        pn := prj.Caption
      else
        pn := S_UNKNOWNPROJECT;
      uid := UserIDOfSource(msg);
      SendLog(uid, LOGID_SVRMNG_SOMEONEVERIFYAUTHCODE, LOG_LEVEL_MESSAGE, [cn, pn, ErrCodeToStr(ar.VerifyResult)]);
      ReturnMsg(msg, MESID_SVRMNG_VERIFYAUTHCODERESP, ar);
    end
    else
      Break;
  end;
end;

procedure TSvrMngProcObj.PublishRemoteDesktop(const Remote: TID_HOST; Data: longword;
  Err: ERRCODE; DeskName: CommonString);
var
  so, so1, so2: TStringObj;
  m: TMsg;
begin
  so1 := TStringObj(FRegedRemoteDesks.Item[0]);
  while so1 <> nil do
  begin
    if so1.ObjID.IDDetail.SvrMdl.HostID = Remote then
    begin
      so2 := TStringObj(so1.Item[0]);
      while so2 <> nil do
      begin
        m := GetAMsg;
        m.ObjID := so1.ObjID;
        m.TargetObjID := so2.ObjID;
        m.MsgID := MESID_ALL_DESKTOP_STATUS;
        m.Data := Data;
        m.Error := Err;
        if DeskName <> '' then
        begin
          so := ObjPool.GetObj(TStringObj, Self);
          so.Str := DeskName;
          m.AddObj(so);
        end;
        SendMsg(m);
        so2 := TStringObj(so2.Next);
      end;
      Break;
    end;
    so1 := TStringObj(so1.Next);
  end;
end;

procedure TSvrMngProcObj.ProcRootRouted(msg: TMsg);
var
  S: CommonString;
  n, lu: TDateTime;
  rta: TRunTimeAccount;
begin
  S := QueryHostParamValue(ID_AUTOUPDATE);
  if (msg.Data and I_ROUTESTATUS_ROUTED <> 0) and (S = S_SWITCH_ON) then
  begin
    n := Now;
    lu := StrToDouble(QueryHostParamValue(ID_LASTUPDATE));
    if n - lu >= 0.5 then
      StartSelfUpdate;
  end;
  if FNetQueryObj <> nil then
  begin
    rta := FNetQueryObj.QueryRuntimeAccount(HOSTID_ROOT);
    if (msg.Data and I_ROUTESTATUS_LOGINED <> 0) and (rta <> nil) then
    begin
      FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(rta.AccountID));
      SendMsgToObj(ROOTID_SVRMNGMODULE, MESID_SVRMNG_GET_SYNCCONFIG, 1);
    end;
  end;
end;

procedure TSvrMngProcObj.ProcRouteFail(msg: TMsg);
var
  m: TMsg;
begin
  while msg.Count > 0 do
  begin
    m := msg.Param(TMsg);
    if m <> nil then
    begin
      if m.MsgID = MESID_ALL_DESKTOP_START then
      begin
        m.Data := I_DESKSTATUS_STARTED;
        m.MsgID := MESID_SVRMNG_REMOTEDESKTOPSTATUS;
        m.Error := ERRCODE_NOAVAILUI;
        NotifyFocus(ID_DESKFUNC, m);
      end
      else
        ObjPool.ReturnObj(m, Self);
    end
    else
      Break;
  end;
end;

function TSvrMngProcObj.ReplaceFile(UpdatePath: CommonString; UpFileNames, DepFileNames: TFILE_LIST): boolean;
var
  i: integer;
  S, exp: CommonString;
begin
  Result := False;
  RegularPath(UpdatePath);
  exp := ModuleFilePath;
  for i := 0 to High(UpFileNames) do
  begin
    S := exp + DepFileNames[i];
    if not DeleteFileW(pwidechar(S)) then
    begin
      if FileExists(S) then
        if not MoveFileW(pwidechar(S), pwidechar(S + EXT_OLD)) then
          Exit;
    end;
    if not CopyFileW(pwidechar(UpdatePath + UpFileNames[i]), pwidechar(S), False) then
      Exit;
  end;
  Result := True;
end;

procedure TSvrMngProcObj.RequestFileSystem(Path: CommonString; const ReqID: TID_OBJ);
var
  a: array [0 .. 499] of CommonChar;
  p: PCommonChar;
  fp: TFilePath;
  m: TMsg;
  sr: TSearchRec;
  sf: TFileInfo;
  S: CommonString;
  OnlyDir: boolean;
begin
  if (Path <> '') and (Path[Length(Path)] = ';') then
  begin
    OnlyDir := True;
    System.Delete(Path, Length(Path), 1);
  end
  else
    OnlyDir := False;
  m := GetAMsg;
  m.TargetObjID := ReqID;
  m.MsgID := MESID_SVRMNG_HOSTFILESYSTEMINFO;
  m.Caption := Path;
  if (Path = '') or (Path = PathDelim) then
  begin
    if GetLogicalDriveStrings(SizeOf(a), a) > 0 then
    begin
      p := a;
      while p^ <> #0 do
      begin
        fp := ObjPool.GetObj(TFilePath, Self);
        fp.Caption := p;
        m.AddObj(fp);
        Inc(p, Length(fp.Caption) + 1);
      end;
    end;
  end
  else
  begin
    fp := ObjPool.GetObj(TFilePath, Self);
    fp.Caption := '..';
    m.AddObj(fp);
    Path := RegularPath(Path);
    if FindFirst(Path + '*.*', faAnyFile, sr) = 0 then
    begin
      while FindNext(sr) = 0 do
        if sr.Name <> '..' then
          if sr.Attr and faDirectory <> 0 then
          begin
            fp := ObjPool.GetObj(TFilePath, Self);
            fp.Caption := sr.Name;
            m.AddObj(fp);
          end
          else if not OnlyDir then
          begin
            S := Path + sr.Name;
            sf := ObjPool.GetObj(TFileInfo, Self);
            sf.Caption := sr.Name;
            sf.size := GetFileSize(S);
            sf.ModTime := FileTimeToFileDate(GetFileModifyTime(S));
            m.AddObj(sf);
          end;
    end;
  end;
  SendMsg(m);
end;

function TSvrMngProcObj.RequestNewPathFile: longword;
var
  si: TSyncItem;
  sp: TSearchParam;
  m: TMsg;
  rta: TRunTimeAccount;

  procedure CheckNewObj(Path: TSerialObj);
  var
    oid: PID_OBJ;
    tmpp, tmpr: TSerialObj;
  begin
    tmpp := TSerialObj(Path.Item[0]);
    while tmpp <> nil do
    begin
      if tmpp.ObjID.IsEmpty then
      begin
        oid := @TSerialObj(tmpp.Parent).ObjID;
        tmpr := TSyncPath(m.FindItemLowerLevel(oid^));
        if tmpr = nil then
        begin
          tmpr := ObjPool.GetObj(TSyncPath, Self);
          tmpr.ObjID := oid^;
          m.AddObj(tmpr);
        end;
        Inc(Result);
        tmpr.AddObj(tmpp.Clone(False));
      end
      else if tmpp.Count > 0 then
        CheckNewObj(tmpp);
      tmpp := TSerialObj(tmpp.Next);
    end;
  end;

begin
  Result := 0;
  rta := FNetQueryObj.QueryRuntimeAccount(HOSTID_ROOT);
  if rta <> nil then
  begin
    si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(rta.AccountID));
    if si <> nil then
    begin
      sp := TSearchParam(si.Item[0]);
      while sp <> nil do
      begin
        if sp.CheckFlag(SPFLAG_INUPDATE) then
        begin
          m := GetAMsg;
          CheckNewObj(TSyncPath(sp.Item[0]));
          if m.Count > 0 then
          begin
            m.ObjID := sp.ObjID;
            m.TargetObjID := ROOTID_SVRMNGMODULE;
            m.MsgID := MESID_SVRMNG_ADD_SYNCPATHFILEENTRY;
            SendMsg(m);
          end
          else
          begin
            ObjPool.ReturnObj(m, Self);
            SyncFileOpe;
          end;
        end;
        sp := TSearchParam(sp.Next);
      end;
    end;
  end;
end;

procedure TSvrMngProcObj.Reset;
begin
  inherited;
  if FSvrStatic = nil then
    FSvrStatic := TSvrStaticObj(ObjPool.GetObj(TSvrStaticObj, Self));
  if FSyncCenterConfig = nil then
    FSyncCenterConfig := TSerialFile.Create;
  if FRegedRemoteDesks = nil then
    FRegedRemoteDesks := TSerialObj.Create;
end;

procedure TSvrMngProcObj.Return;
begin
  if FSyncSearchThread <> nil then
  begin
    FSyncSearchThread.Terminate;
    FSyncSearchThread := nil;
  end;
  FreeAndNil(FRegedRemoteDesks);
  FreeAndNil(FSyncCenterConfig);
  ObjPool.ReturnObj(FSvrStatic, Self);
  FSvrStatic := nil;
  inherited;
end;

procedure TSvrMngProcObj.SyncSearchComplete(Sender: TObject; R: TStringObj);
var
  m: TMsg;
  ss: TSyncStaticInfo;
  so: TStringObj;
  s: CommonString;
begin
  if R <> nil then
  begin
    ss := TSearchParam(R.Parent).Param(TSyncStaticInfo);
    if ss <> nil then
    begin
      s := Format('Need upload %d, need download %d', [ss.UploadFilesCount, ss.DownloadFilesCount]);
      if not FCurSyncCntID.IsEmpty then
      begin
        so := ObjPool.GetObj(TStringObj, Self);
        so.Str := s;
        SendMsgToObj(FCurSyncCntID, MESID_SVRMNG_SYNCPROGRESS, [so]);
      end;
      Dbg(s);
    end;
    StartSearchThread;
    m := GetAMsg;
    m.TargetObjID := FullObjID;
    m.MsgID := MESID_SVRMNG_SEARCH_COMPLATE;
    SendMsg(m);
  end;
end;

procedure TSvrMngProcObj.SyncSearchTerminate(Thread: TThread);
begin
  FSyncSearchThread := nil;
end;

procedure TSvrMngProcObj.SelfUpdate;
var
  fl, df: TFILE_LIST;
begin
  CompireUpdateFile(FUpdateableFilePath, fl, df);
  ReplaceFile(FUpdateableFilePath, fl, df);
  SetLength(fl, 0);
  SetLength(df, 0);
end;

procedure TSvrMngProcObj.SetProperty(PropertyName, PropertyValue: CommonString);
begin
  // if PropertyName = S_PROPERTY_ROOTPRODUCTID then
  // FRootProductID.FromStr(PropertyValue)
  if PropertyName = S_PROPERTY_PRODUCTID then
    FCurProductID.FromStr(PropertyValue)
  else if PropertyName = S_PROJECTFILES_PATH then
    FUpdateableFilePath := RegularPath(PropertyValue)
  else
    inherited;
end;

function TSvrMngProcObj.IndependenceExecThread: boolean;
begin
  Result := True;
end;

procedure TSvrMngProcObj.StartSearchThread;
var
  si: TSyncItem;
  rta: TRunTimeAccount;

  procedure EnumInSyncItem;
  var
    sp: TSearchParam;
  begin
    sp := TSearchParam(si.Item[0]);
    while sp <> nil do
    begin
      if sp.CheckFlag(SPFLAG_WAITENUM) then
      begin
        FSyncSearchThread.SearchParam := sp;
        FSyncSearchThread.Resume;
        Exit;
      end;
      sp := TSearchParam(sp.Next);
    end;
  end;

begin
  rta := FNetQueryObj.QueryRuntimeAccount(HOSTID_ROOT);
  if (FullObjID.IDDetail.SvrMdl.HostID = HOSTID_ROOT) or (rta <> nil) then
  begin
    if FSyncSearchThread = nil then
    begin
      FSyncSearchThread := TSearchThread.Create('Sync enum thread');
      FSyncSearchThread.OnSearchComplete := SyncSearchComplete;
      FSyncSearchThread.OnTerminate := SyncSearchTerminate;
    end;
    if not FSyncSearchThread.Busy then
    begin
      if rta <> nil then
      begin
        si := FSyncCenterConfig.GetStoreList(GenSyncTableIDByUID(rta.AccountID));
        EnumInSyncItem;
      end
      else
      begin
        si := TSyncItem(FSyncCenterConfig.Item[0]);
        while si <> nil do
        begin
          EnumInSyncItem;
          si := TSyncItem(si.Next);
        end;
      end;
    end;
  end;
end;

procedure TSvrMngProcObj.StartSelfUpdate;
var
  dc: TDevClient;
begin
  dc := Param(TDevClient);
  if dc = nil then
  begin
    dc := ObjPool.GetObj(TDevClient, Self);
    AddObj(dc);
    dc.ProductID := FCurProductID;
    dc.StartUpdate;
  end;
end;

procedure TSvrMngProcObj.StartUpdateableMonitor;
var
  so: TStringObj;
begin
  so := ObjPool.GetObj(TStringObj, Self);
  so.Str := FUpdateableFilePath;
  SendMsgToObj(OBJID_SVRMODULE, MESID_NET_ADDPATHMONITOR, [so]);
end;

procedure TSvrMngProcObj.UpdateableFileChanged(ct: TFileChangeType; oldfilename, newfilename: CommonString);
var
  fs: TStoreList;
  ufi: TUpdateableFileInfo;
  prj: TUpdateableProject;

  function FileBelongRootProject(const fid: TID_OBJ): boolean;
  var
    uo: TUpdateFileObj;
  begin
    Result := False;
    if prj <> nil then
    begin
      uo := TUpdateFileObj(prj.Item[0]);
      while uo <> nil do
      begin
        if uo.FileID = fid then
        begin
          Result := True;
          Break;
        end;
        uo := TUpdateFileObj(uo.Next);
      end;
    end;
  end;

begin
  Dbg('File change notify [%s]: %s - %s', [S_FILECHANGETYPE[ct], oldfilename, newfilename]);
  case ct of
    fctAdd:
      ;
    fctRemove:
      ;
    fctModify:
    begin
      fs := ObjTree.FindTable(I_UPDATEABLEFILETABLE);
      if fs <> nil then
      begin
        ufi := TUpdateableFileInfo(fs.FindItemLowerLevel(oldfilename));
        if ufi <> nil then
        begin
          if (FullObjID.IDDetail.SvrMdl.HostID = HOSTID_ROOT) and not FCurProductID.IsEmpty then
          begin
            prj := TUpdateableProject(ObjTree.FindTable(FCurProductID));
            if FileBelongRootProject(ufi.ObjID) then
            begin
              Dbg('File belong ROOT product, so restart application after 10 second.');
              FRestartDownCounter := 10;
              Exit;
            end;
          end;
          Dbg('File belong other product, so retreat ver info after 5 second.');
          FRetryVerDownCounter := 5;
        end;
      end;
    end;
    fctRename:
      ;
  end;
end;

procedure TSvrMngProcObj.UpdatePrjAdded(Sender: TObject; ParObj, Obj: TSerialObj; const sid, uid: TID_OBJ);
var
  tbl: TStoreList absolute Obj;
  m: TMsg;
begin
  tbl.AllowSameName := True;
  tbl.CanAddRecord := CanAddUpdateableObj;
  tbl.GetRecords := GetTableRecords;
  m := GetAMsg;
  m.TargetObjID := OBJID_UNIQUEDATAMODULE;
  m.MsgID := MESID_UNIQUEDATA_ADDSTOREFILE;
  m.ConnObj := Obj;
  SendMsg(m);
  InitResource(tbl.ObjID, MASK_ALL, 0, tbl.Caption);
  InitPriority(tbl.ObjID, MASK_ALL, '', MESID_OPERATOR_ACCESS);
  InitPriorityAllOpe(tbl.ObjID, ID_USERROOT_LOCAL, '');
end;

procedure TSvrMngProcObj.UpFileDeled(Sender: TObject; Obj: TSerialObj; const sid, uid: TID_OBJ);
var
  ps: TStoreList;
  prj: TUpdateableProject;
  ufo: TUpdateFileObj;
begin
  Dbg('Update file %s(%s) deled, so del entry of Update Projects', [Obj.Caption, Obj.ObjID.ToString]);
  ps := ObjTree.FindTable(I_UPDATEABLEPROJECTTABLE);
  if ps <> nil then
  begin
    prj := TUpdateableProject(ps.Item[0]);
    while prj <> nil do
    begin
      ufo := TUpdateFileObj(prj.Item[0]);
      while ufo <> nil do
      begin
        if ufo.FileID = Obj.ObjID then
          DelRecord(ufo.ObjID);
        ufo := TUpdateFileObj(ufo.Next);
      end;
      prj := TUpdateableProject(prj.Next);
    end;
  end;
end;

initialization

  ObjPool.RegisterObjClass(TSvrMngProcObj);

end.
